#include "luster_build.hh"

#include <inttypes.h>
#include <stdint.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#include "luster_config.hh"
#include "luster_fs.hh"
#include "luster_log.hh"
#include "luster_ownership.hh"
#include "luster_status.hh"
#include "luster_string.hh"
#include "luster_subproc.hh"
#include "luster_time.hh"

namespace luster {

static char const *LUSTER_MODULES[] = {
	"alloc",
	"build",
	"fs",
	"c",
	"dynarr",
	"lexpr",
	"log",
	"parser",
	"st0",
	"status",
	"string",
	"subproc",
	"test",
	"vm",
};

static int compare_timespec(
	struct timespec const *lhs, struct timespec const *rhs) {

	int64_t diff_sec = lhs->tv_sec - rhs->tv_sec;
	int64_t diff_nsec = lhs->tv_nsec - rhs->tv_nsec;

	diff_nsec += diff_sec * 1000 * 1000 * 1000 * 100;

	return diff_nsec == 0 ? 0 : (diff_nsec > 0 ? 1 : -1);
}

typedef struct target_ood_checker {
	struct stat dst;
	bool is_ood;
} target_ood_checker_t;

void ood_init(target_ood_checker_t *ood, char const *dst) {
	bool stat_ok = stat(dst, &ood->dst) == 0;
	ood->is_ood = !stat_ok;
}

Status ood_check(target_ood_checker_t *ood, char const *src) {
	// 当前目标已经过期就不再检查新的文件。
	if (ood->is_ood) {
		return STATUS_OK;
	}

	struct stat src_stat;

	bool stat_ok = stat(src, &src_stat) == 0;
	if (!stat_ok) {
		logprint(LOG_ERROR, "source not exists: %s", src);
		return STATUS_ERROR;
	}

#if defined(__APPLE__)
	ood->is_ood = compare_timespec(&src_stat.st_mtimespec,
			      &ood->dst.st_mtimespec) > 0;
#else
	ood->is_ood =
		compare_timespec(&src_stat.st_mtim, &ood->dst.st_mtim) > 0;
#endif

	return STATUS_OK;
}

struct Link {
	Cmd cmd;
};

static void link_init(Link *cl, char const *path) {
	cmd_clear(&cl->cmd);

	cmd_append(&cl->cmd, "c++");
	cmd_append(&cl->cmd, "-rdynamic");
	if (luster_CFG(ASAN))
		cmd_append(&cl->cmd, "-lasan");
	cmd_append(&cl->cmd, "-o");
	cmd_append(&cl->cmd, path);
}

static void link_run(Link *cl, char const *obj) {
	cmd_append(&cl->cmd, obj);
}

static Status clink_run(ChildPool *pool, Link *cl) {
	return child_pool_spawn(pool, &cl->cmd);
}

static Status cc(ChildPool *pool, char const *src, char const *dst) {
	Cmd cmd;

	cmd_append(&cmd, "c++");
	cmd_append(&cmd, "-fno-exceptions");

	if (luster_CFG(ASAN))
		cmd_append(&cmd, "-fsanitize=address");

	cmd_append(&cmd, "-O2");
	cmd_append(&cmd, "-g");
	cmd_append(&cmd, "-c");
	cmd_append(&cmd, "-o");
	cmd_append(&cmd, dst);
	cmd_append(&cmd, src);

	luster_TRY(child_pool_spawn(pool, &cmd));

	return STATUS_OK;
}

#define luster_ARRSIZE(M_arr) (sizeof(M_arr) / sizeof(*M_arr))

static Status build(void) {
	StringBuffer src;
	StringBuffer obj;

	luster_OWN(src);
	luster_OWN(obj);

	luster_TRY(mkdir_all("build/obj"));
	luster_TRY(mkdir_all("build/bin"));
	luster_TRY(mkdir_all("build/lib"));

	StopWatch watch_cc;

	watch_cc.Start();

	ChildPool pool;
	luster_OWN(pool);

	child_pool_init(&pool, 8);

	for (uintptr_t i = 0; i < luster_ARRSIZE(LUSTER_MODULES); ++i) {
		stringbuf_clear(&src);

		stringbuf_append_cstring(&src, "luster_");
		stringbuf_append_cstring(&src, LUSTER_MODULES[i]);
		stringbuf_append_cstring(&src, ".cc");

		stringbuf_clear(&obj);
		stringbuf_append_cstring(&obj, "build/obj/");
		stringbuf_append_cstring(&obj, LUSTER_MODULES[i]);
		stringbuf_append_cstring(&obj, ".o");

		luster_TRY(cc(&pool, stringbuf_get(&src), stringbuf_get(&obj)));
	}

	luster_TRY(child_pool_wait(&pool));

	int64_t cc_cost = watch_cc.CostMs();

	logprint(LOG_INFO, "compile cost %" PRIi64 " ms", cc_cost);

	Link clink;

	link_init(&clink, "build/bin/luster_st0");

	for (uintptr_t i = 0; i < luster_ARRSIZE(LUSTER_MODULES); ++i) {
		stringbuf_clear(&obj);

		stringbuf_append_cstring(&obj, "build/obj/");
		stringbuf_append_cstring(&obj, LUSTER_MODULES[i]);
		stringbuf_append_cstring(&obj, ".o");

		link_run(&clink, stringbuf_get(&obj));
	}

	luster_TRY(clink_run(&pool, &clink));
	luster_TRY(child_pool_wait(&pool));

	return STATUS_OK;
}

Status luster_cmd_build(void) {
	StopWatch watch;

	watch.Start();
	Status status = build();
	int64_t cost = watch.CostMs();

	logprint(LOG_INFO, "build cost %" PRIi64 " ms", cost);

	return status;
}

} // namespace luster

#if luster_CFG(BOOTSTRAP)
int main(void) {
	luster::luster_cmd_build();
	return 0;
}
#endif
